using System.Collections.Concurrent;

namespace MultiAgentsShared;

public interface IEventBus
{
    Guid Id { get; set; }
    void Publish<T>(T @event);
    Subscription<T> Subscribe<T>(Action<T> action);
}

public class EventBus : IEventBus
{
#if FRONTEND
    //TODO: this should not use singleton
    private static readonly Lazy<EventBus> _instance = new Lazy<EventBus>(() => new EventBus());

    // Public property to access the single instance
    public static EventBus Instance => _instance.Value;
#endif

    private readonly ConcurrentDictionary<Type, List<object>> _subscriptions = new();

    // Private constructor to prevent instance creation outside of this class
    public EventBus()
    {
        Id = Guid.NewGuid();
    }

    public Guid Id { get; set; }

    public void Publish<T>(T @event)
    {
        if (_subscriptions.TryGetValue(typeof(T), out List<object>? subscribers))
        {
            foreach (var subscriber in subscribers.ToList())
            {
                try
                {
                    // Execute each subscriber's action in its own task to avoid blocking and handle exceptions
                    var action = (Action<T>)subscriber;
                    Task.Run(() => action(@event)).ContinueWith(task =>
                    {
                        if (task.Exception != null)
                        {
                            // Log or handle the exception
                            Console.WriteLine(task.Exception);
                        }
                    });
                }
                catch (Exception ex)
                {
                    // Log or handle the exception thrown by starting the task
                    Console.WriteLine(ex);
                }
            }
        }
    }

    public Subscription<T> Subscribe<T>(Action<T> action)
    {
        var subscribers = _subscriptions.GetOrAdd(typeof(T), []);
        subscribers.Add(action);

        return new Subscription<T>(_subscriptions, action);
    }
}

// Subscription class used to handle disposal
public class Subscription<T>(ConcurrentDictionary<Type, List<object>> subscriptions, Action<T> action) : IDisposable
{
    private readonly ConcurrentDictionary<Type, List<object>> _subscriptions = subscriptions;
    private readonly Action<T> _action = action;

    public void Dispose()
    {
        if (_subscriptions.TryGetValue(typeof(T), out List<object>? subscribers))
        {
            subscribers.Remove(_action);
        }
    }
}
